package user

import (
	"context"
	"gitee.com/bobo-rs/creative-framework/pkg/sredis"
	"gitee.com/bobo-rs/creative-framework/pkg/utils"
	"gitee.com/bobo-rs/innovideo-services/consts"
	"gitee.com/bobo-rs/innovideo-services/enums"
	"gitee.com/bobo-rs/innovideo-services/framework/dao"
	"gitee.com/bobo-rs/innovideo-services/framework/model"
	"gitee.com/bobo-rs/innovideo-services/framework/model/entity"
	"gitee.com/bobo-rs/innovideo-services/framework/service"
	"gitee.com/bobo-rs/innovideo-services/library/exception"
	"gitee.com/bobo-rs/innovideo-services/library/services/jwt"
	"gitee.com/bobo-rs/innovideo-services/library/tools"
	"github.com/gogf/gf/v2/errors/gerror"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/net/ghttp"
	"time"
)

// LoginAuthParseToken 通过Token授权认证登录信息是否有效
func (u sUser) LoginAuthParseToken(ctx context.Context, token string) error {
	// 解析token
	userAuth, err := u.ParseToken(ctx, token)
	if err != nil {
		return err
	}

	var (
		detail    *model.UserBasicDetailItem
		cacheKey  = tools.LoginAuthTokenKey(userAuth.Uid, userAuth.AccountToken)
		cacheFunc = func(ctx context.Context) (value interface{}, err error) {
			// 验证当前用户登录设备
			err = u.AuthLoginDevice(ctx, userAuth.AccountToken)
			if err != nil {
				return nil, err
			}
			// 获取用户详情信息
			err = u.ProcessUserDetailByUid(ctx, userAuth.Uid, &detail)
			if err != nil {
				return nil, exception.New(`账户异常，用户信息为空`)
			}
			// 获取账户名和昵称
			accountItem, err := u.ProcessAccountNameByUid(ctx, userAuth.Uid)
			if err != nil {
				return nil, exception.New(`账户信息异常`)
			}
			detail.Nickname = accountItem.Nickname
			return detail, nil
		}
	)

	// 缓存用户详情
	r, err := sredis.NewCache().GetOrSetFunc(ctx, cacheKey, cacheFunc, consts.LoginAuthExpire)
	if err != nil {
		return err
	}

	// 断言扫描
	if err = r.Struct(&detail); err != nil {
		return exception.Newf(`账户信息异常%s`, err.Error())
	}

	// 设置用户基础信息
	service.BizCtx().SetUser(ctx, userAuth)
	service.BizCtx().SetDataValue(ctx, enums.ContextDataUser, detail)
	return nil
}

// Logout 退出登录
func (u *sUser) Logout(ctx context.Context) error {
	userDetail := service.BizCtx().GetUser(ctx)
	if userDetail == nil {
		return nil
	}
	return u.AfterLogout(ctx, &model.LoginAfterLogoutItem{
		Uid:          userDetail.Uid,
		AccountToken: userDetail.AccountToken,
		LoginStatus:  enums.LoginOauthStatusOL,
	})
}

// ForceUserDeviceOffline 当前用户或管理员强制下线登录后的设备
func (u *sUser) ForceUserDeviceOffline(ctx context.Context, deviceId uint) error {
	var (
		orm    = dao.UserLogin.Ctx(ctx).Where(dao.UserLogin.Columns().Id, deviceId)
		user   = service.BizCtx().GetUser(ctx)
		detail model.UserLoginForceOfflineItem
	)
	// 当前用户
	if user != nil && user.IsAdmin == false {
		orm = orm.Where(dao.UserLogin.Columns().UserId, user.Uid)
	}
	err := orm.Scan(&detail)
	if err != nil {
		return gerror.Wrapf(err, `当前登录设备不存在%s`, err.Error())
	}

	// 强制下线设备
	return u.AfterLogout(ctx, &model.LoginAfterLogoutItem{
		Uid:          detail.UserId,
		AccountToken: detail.Token,
		LoginStatus:  enums.LoginOauthStatusTimeout,
	})
}

// CurrUserLoginDeviceList 获取当前用户登陆设备列表
func (u *sUser) CurrUserLoginDeviceList(ctx context.Context) (out *model.CurrUserLoginDeviceListOutput, err error) {
	out = &model.CurrUserLoginDeviceListOutput{}
	// 获取登录设备列表
	err = u.LoginModel().Scan(ctx, g.Map{
		dao.UserLogin.Columns().UserId: service.BizCtx().GetUid(ctx),
		dao.UserLogin.Columns().Status: enums.LoginOauthStatusOk,
	}, &out.Rows)
	return
}

// SaveUserLogin 保存用户登录日志
func (u *sUser) SaveUserLogin(ctx context.Context, login entity.UserLogin) error {
	// 记录登录日志
	if _, err := dao.UserLogin.Ctx(ctx).OmitEmpty().Save(login); err != nil {
		return gerror.Wrapf(err, `登录失败，记录登录信息失败%s，请重试`, err.Error())
	}
	return nil
}

// ProcessUserLoginAfter 用户登录日志-后置处理
func (u *sUser) ProcessUserLoginAfter(ctx context.Context, item model.UserLoginAfterItem) error {
	// 初始化
	login := entity.UserLogin{
		UserId:        item.UserId,
		LoginIp:       utils.GetIp(ctx),
		Token:         utils.ToSha256([]byte(item.Token)),
		DeviceType:    item.DeviceType,
		DeviceContent: ghttp.RequestFromCtx(ctx).UserAgent(),
		Mac:           item.Mac,
	}

	// 解析时间
	t, err := time.Parse(time.DateTime, item.Expire)
	if err != nil {
		return gerror.Wrapf(err, `登录异常，解析效期时间失败%s`, err.Error())
	}
	login.ExpireTime = uint64(t.In(time.Local).Unix())
	if item.IsNewDevice {
		login.IsNewDevice = 1 // 新设备
	}

	// 登录设备
	if err = u.SaveUserLogin(ctx, login); err != nil {
		return err
	}
	return nil
}

// ParseToken 解析授权登录Token
func (u *sUser) ParseToken(ctx context.Context, token string) (*model.UserAccountItem, error) {
	claims, err := jwt.New(ctx).ParseToken(token)
	if err != nil {
		return nil, exception.NewCode(enums.ErrorNotLogoutIn, err.Error())
	}
	return &model.UserAccountItem{
		Uid:          claims.Uid,
		Account:      claims.Account,
		Nickname:     claims.Nickname,
		AccountToken: utils.ToSha256([]byte(token)),
	}, nil
}

// AuthLoginDevice 验证登录信息【后台或用户可强制下线】
func (u *sUser) AuthLoginDevice(ctx context.Context, accountToken string) error {
	detail := &model.UserLoginCurrLoginItem{}
	err := u.ProcessLoginDetailByToken(
		ctx, accountToken, &detail,
	)
	if err != nil {
		return err
	}
	// 是否存在
	if detail == nil {
		return exception.NewCode(enums.ErrorNotLogoutIn, ``)
	}
	// 是否已下线
	if enums.LoginOauthStatus(detail.Status) != enums.LoginOauthStatusOk {
		return exception.NewCodef(enums.ErrorNotLogoutIn, `已%s，请登录`, enums.LoginOauthStatus(detail.Status).Fmt())
	}
	// 是否过期
	if detail.ExpireTime < uint(time.Now().Unix()) {
		return exception.NewCode(enums.ErrorNotLogoutIn, `登录已过期`)
	}
	return nil
}

// OfflineLoginDevice 下线用户登录设备
func (u *sUser) OfflineLoginDevice(ctx context.Context, accountToken string, status enums.LoginOauthStatus) error {
	if len(accountToken) == 0 {
		return gerror.New(`缺少登录TOKEN`)
	}
	// 下线登录设备
	_, err := dao.UserLogin.Ctx(ctx).
		Where(dao.UserLogin.Columns().Token, accountToken).
		Data(dao.UserLogin.Columns().Status, status).
		Update()
	if err != nil {
		return gerror.Wrapf(err, `用户%s失败, 请重试`, status.Fmt())
	}
	return nil
}

// AfterLogout 用户退出登录-后置处理
func (u *sUser) AfterLogout(ctx context.Context, after *model.LoginAfterLogoutItem) error {
	// 移除JWT缓存Token
	err := jwt.New(ctx).RemoveHook(after.AccountToken)
	if err != nil {
		return err
	}

	// 销毁登录缓存
	_ = u.RemoveLoginCache(ctx, after.Uid, after.AccountToken)

	// 下线设备
	return u.OfflineLoginDevice(ctx, after.AccountToken, after.LoginStatus)
}

// RemoveLoginCache 移除登录缓存信息（用户详情、管理员详情）-适用于（退出登录、强制退出、修改用户信息等）
func (u *sUser) RemoveLoginCache(ctx context.Context, uid uint, accountToken string) error {
	// 缓存服务
	return sredis.NewCache().Removes(ctx, []interface{}{
		tools.LoginAuthTokenKey(uid, accountToken),                    // 移除用户信息
		tools.CacheKey(consts.LoginAdminAuthToken, accountToken),      // 移除管理员信息
		tools.CacheKey(consts.LoginAdminAuthPermMenu, accountToken),   // 移除管理员权限菜单
		tools.CacheKey(consts.LoginAdminAuthPermAction, accountToken), // 移除管理员操作码
		tools.CacheKey(consts.LoginAdminAuthPermId, accountToken),     // 移除管理员权限
	})
}
